Skip to main content

Oracle NetSuite

This guide walks you through the steps required to connect Docflo.ai to your Oracle NetSuite system, enabling seamless document integration and automated invoice processing.

πŸ“‹ Prerequisites:
  • Oracle NetSuite account with SuiteScript and REST API access
  • Visual Studio or development environment for .NET
  • Docflo.ai account with API access
  • NetSuite user with appropriate roles and permissions
  • SSL certificate management capabilities
  • NetSuite Integration Record and Token-Based Authentication setup

πŸš€ Integration Steps​

Follow these steps to establish a secure connection between Docflo.ai and your Oracle NetSuite system:

Step 1: Import SSL Certificate Chain​

  1. Download our SSL certificate chain (link)
  2. Import the certificate to the Windows Certificate Store on your integration server
  3. Add the certificate to Trusted Root Certification Authorities
  4. Ensure the certificate is properly validated for HTTPS connections

Step 2: Configure NetSuite Token-Based Authentication​

  1. Navigate to Setup > Integration > Manage Integrations in NetSuite
  2. Create a new Integration Record:
    • Name: Docflo Integration
    • Description: Integration with Docflo.ai for document processing
    • State: Enabled
    • Token-Based Authentication: Checked
  3. Save and note down:
    • Consumer Key
    • Consumer Secret
  4. Create Access Token:
    • Go to Setup > Users/Roles > Access Tokens
    • Click "New Access Token"
    • Select the Integration Record created above
    • Select appropriate user and role
    • Note down Token ID and Token Secret

Step 3: Configure NetSuite User Permissions​

Ensure your integration user has the following permissions:

  1. Transactions:

    • Vendor Bill: Create, Edit, View
    • Invoice: Create, Edit, View
    • Purchase Order: View
    • Sales Order: View
  2. Lists:

    • Vendors: Create, Edit, View
    • Customers: Create, Edit, View
    • Items: View
    • Accounts: View
  3. Setup:

    • REST Web Services: Full
    • SuiteScript: Full

Step 4: Generate API Credentials​

  1. Go to the "Integrations" section in your Docflo.ai platform
  2. Create an API key for NetSuite integration
  3. Copy the API key and store it securely
  4. Copy the tenant ID as well - you'll need both for the integration

Step 5: Create NetSuite Integration Service​

Create a Windows Service or scheduled application using NetSuite's REST API with OAuth 1.0. Here's a sample C# implementation:

using System;
using System.Collections.Generic;
using System.Net.Http;
using System.Text;
using System.Threading.Tasks;
using Newtonsoft.Json;
using System.Net.Http.Headers;
using System.Linq;
using System.Security.Cryptography;
using System.Web;

namespace DocfloNetSuiteIntegration
{
public class DocfloNetSuiteIntegrationService
{
private readonly string _docfloApiUrl;
private readonly string _tenantId;
private readonly string _apiKey;
private readonly string _netSuiteAccountId;
private readonly string _consumerKey;
private readonly string _consumerSecret;
private readonly string _tokenId;
private readonly string _tokenSecret;
private readonly string _netSuiteBaseUrl;
private readonly HttpClient _httpClient;
private readonly HttpClient _netSuiteClient;

public DocfloNetSuiteIntegrationService(string docfloApiUrl, string tenantId, string apiKey,
string netSuiteAccountId, string consumerKey, string consumerSecret,
string tokenId, string tokenSecret)
{
_docfloApiUrl = docfloApiUrl;
_tenantId = tenantId;
_apiKey = apiKey;
_netSuiteAccountId = netSuiteAccountId;
_consumerKey = consumerKey;
_consumerSecret = consumerSecret;
_tokenId = tokenId;
_tokenSecret = tokenSecret;
_netSuiteBaseUrl = $"https://{_netSuiteAccountId}.suitetalk.api.netsuite.com/rest/platform/v1";

// Initialize Docflo HTTP client
_httpClient = new HttpClient();
_httpClient.DefaultRequestHeaders.Add("x-tenant-id", _tenantId);
_httpClient.DefaultRequestHeaders.Add("apiKey", _apiKey);

// Initialize NetSuite HTTP client
_netSuiteClient = new HttpClient();
_netSuiteClient.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
}

private string GenerateOAuthHeader(string httpMethod, string url)
{
var timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds().ToString();
var nonce = Guid.NewGuid().ToString("N");

var parameters = new SortedDictionary<string, string>
{
{"oauth_consumer_key", _consumerKey},
{"oauth_nonce", nonce},
{"oauth_signature_method", "HMAC-SHA256"},
{"oauth_timestamp", timestamp},
{"oauth_token", _tokenId},
{"oauth_version", "1.0"}
};

var parameterString = string.Join("&", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}={Uri.EscapeDataString(p.Value)}"));
var signatureBaseString = $"{httpMethod.ToUpper()}&{Uri.EscapeDataString(url)}&{Uri.EscapeDataString(parameterString)}";
var signingKey = $"{Uri.EscapeDataString(_consumerSecret)}&{Uri.EscapeDataString(_tokenSecret)}";

using (var hmac = new HMACSHA256(Encoding.UTF8.GetBytes(signingKey)))
{
var signature = Convert.ToBase64String(hmac.ComputeHash(Encoding.UTF8.GetBytes(signatureBaseString)));
parameters.Add("oauth_signature", signature);
}

var authHeader = "OAuth " + string.Join(", ", parameters.Select(p => $"{Uri.EscapeDataString(p.Key)}=\"{Uri.EscapeDataString(p.Value)}\""));
return authHeader;
}

public async Task<List<DocfloDocument>> GetDocfloDocuments(string documentType, bool includeResults = true)
{
try
{
string url = $"{_docfloApiUrl}/docs/v1/document?type={documentType}&includeResults={includeResults}";

HttpResponseMessage response = await _httpClient.GetAsync(url);
response.EnsureSuccessStatusCode();

string jsonContent = await response.Content.ReadAsStringAsync();
var docfloResponse = JsonConvert.DeserializeObject<DocfloResponse>(jsonContent);

return docfloResponse.Data;
}
catch (Exception ex)
{
Console.WriteLine($"Error fetching Docflo documents: {ex.Message}");
return new List<DocfloDocument>();
}
}

public async Task<bool> CreateVendorBill(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);

// Ensure vendor exists or create one
var vendorId = await GetOrCreateVendor(extractedData.VendorName, extractedData.VendorCode);

// Create Vendor Bill payload for NetSuite
var billPayload = new
{
entity = new { id = vendorId },
tranDate = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
tranId = extractedData.InvoiceNumber,
memo = $"Vendor bill processed from Docflo document {document.Id}",
currency = new { internalId = GetCurrencyId(extractedData.CurrencyCode) },
exchangeRate = 1.0,
// Custom field for Docflo tracking
custbody_docflo_document_id = document.Id,
custbody_docflo_source_id = document.SourceId,
item = new
{
items = extractedData.LineItems.Select((line, index) => new
{
item = new { internalId = GetOrCreateItem(line.ItemCode, line.Description).Result },
quantity = line.Quantity,
rate = line.UnitPrice,
amount = line.LineTotal,
description = line.Description,
account = new { internalId = GetDefaultExpenseAccount() }
}).ToArray()
}
};

string jsonPayload = JsonConvert.SerializeObject(billPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

string netSuiteUrl = $"{_netSuiteBaseUrl}/record/vendorbill";

// Add OAuth header
var authHeader = GenerateOAuthHeader("POST", netSuiteUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authHeader);

HttpResponseMessage response = await _netSuiteClient.PostAsync(netSuiteUrl, content);

if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Vendor Bill created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating Vendor Bill: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating Vendor Bill: {ex.Message}");
return false;
}
}

public async Task<bool> CreateCustomerInvoice(DocfloDocument document)
{
try
{
// Extract data from Docflo document results
var extractedData = ExtractInvoiceData(document);

// Ensure customer exists or create one
var customerId = await GetOrCreateCustomer(extractedData.CustomerName, extractedData.CustomerCode);

// Create Customer Invoice payload for NetSuite
var invoicePayload = new
{
entity = new { id = customerId },
tranDate = extractedData.InvoiceDate?.ToString("yyyy-MM-dd") ?? DateTime.Now.ToString("yyyy-MM-dd"),
tranId = extractedData.InvoiceNumber,
memo = $"Customer invoice processed from Docflo document {document.Id}",
currency = new { internalId = GetCurrencyId(extractedData.CurrencyCode) },
exchangeRate = 1.0,
// Custom field for Docflo tracking
custbody_docflo_document_id = document.Id,
custbody_docflo_source_id = document.SourceId,
item = new
{
items = extractedData.LineItems.Select((line, index) => new
{
item = new { internalId = GetOrCreateItem(line.ItemCode, line.Description).Result },
quantity = line.Quantity,
rate = line.UnitPrice,
amount = line.LineTotal,
description = line.Description,
taxCode = new { internalId = GetDefaultTaxCode() }
}).ToArray()
}
};

string jsonPayload = JsonConvert.SerializeObject(invoicePayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

string netSuiteUrl = $"{_netSuiteBaseUrl}/record/invoice";

// Add OAuth header
var authHeader = GenerateOAuthHeader("POST", netSuiteUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(authHeader);

HttpResponseMessage response = await _netSuiteClient.PostAsync(netSuiteUrl, content);

if (response.IsSuccessStatusCode)
{
string responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Customer Invoice created successfully for document {document.Id}");
Console.WriteLine($"Response: {responseContent}");
return true;
}
else
{
string errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating Customer Invoice: {response.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return false;
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception creating Customer Invoice: {ex.Message}");
return false;
}
}

public async Task<string> GetOrCreateVendor(string vendorName, string vendorCode = null)
{
try
{
// First, try to find existing vendor by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(vendorCode)
? $"SELECT id FROM vendor WHERE entityid = '{vendorCode}'"
: $"SELECT id FROM vendor WHERE companyname = '{vendorName}'"
};

string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");

var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);

HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);

if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}

// Vendor not found, create new one
var vendorPayload = new
{
entityId = vendorCode ?? GenerateVendorCode(vendorName),
companyName = vendorName ?? "Unknown Vendor",
isPerson = false,
subsidiary = new { internalId = "1" } // Default subsidiary
};

string jsonPayload = JsonConvert.SerializeObject(vendorPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

string createUrl = $"{_netSuiteBaseUrl}/record/vendor";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);

HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);

if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Vendor created: {vendorPayload.entityId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating vendor: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default vendor ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateVendor: {ex.Message}");
return "1"; // Return default vendor ID
}
}

public async Task<string> GetOrCreateCustomer(string customerName, string customerCode = null)
{
try
{
// First, try to find existing customer by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(customerCode)
? $"SELECT id FROM customer WHERE entityid = '{customerCode}'"
: $"SELECT id FROM customer WHERE companyname = '{customerName}'"
};

string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");

var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);

HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);

if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}

// Customer not found, create new one
var customerPayload = new
{
entityId = customerCode ?? GenerateCustomerCode(customerName),
companyName = customerName ?? "Unknown Customer",
isPerson = false,
subsidiary = new { internalId = "1" } // Default subsidiary
};

string jsonPayload = JsonConvert.SerializeObject(customerPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

string createUrl = $"{_netSuiteBaseUrl}/record/customer";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);

HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);

if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Customer created: {customerPayload.entityId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating customer: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default customer ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateCustomer: {ex.Message}");
return "1"; // Return default customer ID
}
}

public async Task<string> GetOrCreateItem(string itemCode, string itemName)
{
try
{
// First, try to find existing item by searching
string searchUrl = $"{_netSuiteBaseUrl}/query/v1/suiteql";
var searchQuery = new
{
q = !string.IsNullOrEmpty(itemCode)
? $"SELECT id FROM item WHERE itemid = '{itemCode}'"
: $"SELECT id FROM item WHERE displayname = '{itemName}'"
};

string searchPayload = JsonConvert.SerializeObject(searchQuery);
var searchContent = new StringContent(searchPayload, Encoding.UTF8, "application/json");

var searchAuthHeader = GenerateOAuthHeader("POST", searchUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(searchAuthHeader);

HttpResponseMessage searchResponse = await _netSuiteClient.PostAsync(searchUrl, searchContent);
if (searchResponse.IsSuccessStatusCode)
{
string searchResponseContent = await searchResponse.Content.ReadAsStringAsync();
var searchResult = JsonConvert.DeserializeObject<NetSuiteSuiteQLResponse>(searchResponseContent);

if (searchResult.Items?.Any() == true)
{
return searchResult.Items.First().Values[0];
}
}

// Item not found, create new service item
var itemPayload = new
{
itemId = itemCode ?? GenerateItemCode(itemName),
displayName = itemName ?? "Service Item",
subsidiary = new { internalId = "1" }, // Default subsidiary
incomeAccount = new { internalId = GetDefaultIncomeAccount() },
expenseAccount = new { internalId = GetDefaultExpenseAccount() }
};

string jsonPayload = JsonConvert.SerializeObject(itemPayload, Formatting.Indented);
var content = new StringContent(jsonPayload, Encoding.UTF8, "application/json");

string createUrl = $"{_netSuiteBaseUrl}/record/serviceitem";
var createAuthHeader = GenerateOAuthHeader("POST", createUrl);
_netSuiteClient.DefaultRequestHeaders.Authorization = AuthenticationHeaderValue.Parse(createAuthHeader);

HttpResponseMessage createResponse = await _netSuiteClient.PostAsync(createUrl, content);

if (createResponse.IsSuccessStatusCode)
{
string createContent = await createResponse.Content.ReadAsStringAsync();
var createResult = JsonConvert.DeserializeObject<NetSuiteCreateResponse>(createContent);
Console.WriteLine($"Service item created: {itemPayload.itemId}");
return createResult.Id;
}
else
{
string errorContent = await createResponse.Content.ReadAsStringAsync();
Console.WriteLine($"Error creating item: {createResponse.StatusCode}");
Console.WriteLine($"Error details: {errorContent}");
return "1"; // Return default item ID
}
}
catch (Exception ex)
{
Console.WriteLine($"Exception in GetOrCreateItem: {ex.Message}");
return "1"; // Return default item ID
}
}

private string GenerateVendorCode(string vendorName)
{
if (string.IsNullOrEmpty(vendorName))
return "VEND" + DateTime.Now.Ticks.ToString().Substring(0, 6);

string vendorCode = vendorName.Replace(" ", "").ToUpper();
if (vendorCode.Length > 10)
vendorCode = vendorCode.Substring(0, 10);

return vendorCode;
}

private string GenerateCustomerCode(string customerName)
{
if (string.IsNullOrEmpty(customerName))
return "CUST" + DateTime.Now.Ticks.ToString().Substring(0, 6);

string custCode = customerName.Replace(" ", "").ToUpper();
if (custCode.Length > 10)
custCode = custCode.Substring(0, 10);

return custCode;
}

private string GenerateItemCode(string itemName)
{
if (string.IsNullOrEmpty(itemName))
return "ITEM" + DateTime.Now.Ticks.ToString().Substring(0, 6);

string itemCode = itemName.Replace(" ", "").ToUpper();
if (itemCode.Length > 15)
itemCode = itemCode.Substring(0, 15);

return itemCode;
}

private string GetCurrencyId(string currencyCode)
{
// Map common currency codes to NetSuite internal IDs
// This should be customized based on your NetSuite setup
switch (currencyCode?.ToUpper())
{
case "USD": return "1";
case "EUR": return "2";
case "GBP": return "3";
case "CAD": return "4";
default: return "1"; // Default to USD
}
}

private string GetDefaultExpenseAccount()
{
// Return default expense account ID - customize based on your chart of accounts
return "120"; // Example: Office Expenses
}

private string GetDefaultIncomeAccount()
{
// Return default income account ID - customize based on your chart of accounts
return "400"; // Example: Sales Revenue
}

private string GetDefaultTaxCode()
{
// Return default tax code ID - customize based on your tax setup
return "1"; // Example: Standard Tax
}

private NetSuiteInvoiceData ExtractInvoiceData(DocfloDocument document)
{
var data = new NetSuiteInvoiceData();
data.LineItems = new List<NetSuiteInvoiceLineItem>();

// Extract data from Docflo results
if (document.DocfloResults?.ModelFields?.Items?.Value != null)
{
foreach (var item in document.DocfloResults.ModelFields.Items.Value)
{
var description = item.ValueObject?.Description;
if (description != null)
{
switch (description.Type?.ToLower())
{
case "vendor_code":
case "supplier_code":
data.VendorCode = description.ValueString;
break;
case "vendor_name":
case "supplier_name":
data.VendorName = description.ValueString;
break;
case "customer_code":
data.CustomerCode = description.ValueString;
break;
case "customer_name":
case "company_name":
data.CustomerName = description.ValueString;
break;
case "invoice_number":
case "document_number":
data.InvoiceNumber = description.ValueString;
break;
case "invoice_date":
case "document_date":
if (DateTime.TryParse(description.ValueString, out DateTime invoiceDate))
{
data.InvoiceDate = invoiceDate;
}
break;
case "currency_code":
case "currency":
data.CurrencyCode = description.ValueString;
break;
case "total":
case "total_amount":
case "gross_amount":
if (double.TryParse(description.ValueString, out double total))
{
data.Total = total;
}
break;
case "item_description":
case "product_description":
case "line_description":
// Create new line item
var lineItem = new NetSuiteInvoiceLineItem
{
Description = description.ValueString,
Quantity = 1, // Default quantity
UnitPrice = 0, // Default price
LineTotal = 0 // Default total
};
data.LineItems.Add(lineItem);
break;
case "item_code":
case "part_number":
// Update last line item code
if (data.LineItems.Count > 0)
{
data.LineItems[data.LineItems.Count - 1].ItemCode = description.ValueString;
}
break;
case "quantity":
// Update last line item quantity
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double quantity))
{
data.LineItems[data.LineItems.Count - 1].Quantity = quantity;
}
break;
case "unit_price":
case "price":
// Update last line item price
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double unitPrice))
{
data.LineItems[data.LineItems.Count - 1].UnitPrice = unitPrice;
}
break;
case "line_total":
case "amount":
// Update last line item total
if (data.LineItems.Count > 0 && double.TryParse(description.ValueString, out double lineTotal))
{
data.LineItems[data.LineItems.Count - 1].LineTotal = lineTotal;
}
break;
}
}
}
}

// Generate codes if not provided
if (string.IsNullOrEmpty(data.CustomerCode) && !string.IsNullOrEmpty(data.CustomerName))
{
data.CustomerCode = GenerateCustomerCode(data.CustomerName);
}

if (string.IsNullOrEmpty(data.VendorCode) && !string.IsNullOrEmpty(data.VendorName))
{
data.VendorCode = GenerateVendorCode(data.VendorName);
}

return data;
}

public void Dispose()
{
_httpClient?.Dispose();
_netSuiteClient?.Dispose();
}
}

// Data models
public class DocfloResponse
{
[JsonProperty("data")]
public List<DocfloDocument> Data { get; set; }
}

public class DocfloDocument
{
[JsonProperty("_id")]
public string Id { get; set; }

[JsonProperty("sourceId")]
public string SourceId { get; set; }

[JsonProperty("sourceDesc")]
public string SourceDesc { get; set; }

[JsonProperty("status")]
public string Status { get; set; }

[JsonProperty("numOfPages")]
public string NumOfPages { get; set; }

[JsonProperty("createdAt")]
public string CreatedAt { get; set; }

[JsonProperty("docflo_results")]
public DocfloResults DocfloResults { get; set; }
}

public class DocfloResults
{
[JsonProperty("modelFields")]
public ModelFields ModelFields { get; set; }
}

public class ModelFields
{
[JsonProperty("Items")]
public ItemsField Items { get; set; }
}

public class ItemsField
{
[JsonProperty("value")]
public List<Item> Value { get; set; }

[JsonProperty("type")]
public string Type { get; set; }
}

public class Item
{
[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("valueObject")]
public ValueObject ValueObject { get; set; }
}

public class ValueObject
{
[JsonProperty("Description")]
public Description Description { get; set; }
}

public class Description
{
[JsonProperty("type")]
public string Type { get; set; }

[JsonProperty("valueString")]
public string ValueString { get; set; }

[JsonProperty("content")]
public string Content { get; set; }
}

public class NetSuiteSuiteQLResponse
{
[JsonProperty("items")]
public List<NetSuiteSuiteQLItem> Items { get; set; }
}

public class NetSuiteSuiteQLItem
{
[JsonProperty("values")]
public List<string> Values { get; set; }
}

public class NetSuiteCreateResponse
{
[JsonProperty("id")]
public string Id { get; set; }

[JsonProperty("success")]
public bool Success { get; set; }
}

public class NetSuiteInvoiceData
{
public string VendorCode { get; set; }
public string VendorName { get; set; }
public string CustomerCode { get; set; }
public string CustomerName { get; set; }
public string InvoiceNumber { get; set; }
public DateTime? InvoiceDate { get; set; }
public string CurrencyCode { get; set; }
public double Total { get; set; }
public List<NetSuiteInvoiceLineItem> LineItems { get; set; }
}

public class NetSuiteInvoiceLineItem
{
public string ItemCode { get; set; }
public string Description { get; set; }
public double Quantity { get; set; }
public double UnitPrice { get; set; }
public double LineTotal { get; set; }
}

// Main program example
class Program
{
static async Task Main(string[] args)
{
var service = new DocfloNetSuiteIntegrationService(
"https://api.docflo.ai", // Replace with actual Docflo API URL
"your-tenant-id", // Replace with your tenant ID
"your-api-key", // Replace with your API key
"your-netsuite-account-id", // Replace with your NetSuite account ID
"your-consumer-key", // Replace with Consumer Key from Integration Record
"your-consumer-secret", // Replace with Consumer Secret from Integration Record
"your-token-id", // Replace with Token ID from Access Token
"your-token-secret" // Replace with Token Secret from Access Token
);

try
{
// Get documents from Docflo
var documents = await service.GetDocfloDocuments("invoice", true);

foreach (var document in documents)
{
// Process each document based on status
if (document.Status == "APPROVED")
{
// Create Vendor Bill for vendor invoices
bool vendorResult = await service.CreateVendorBill(document);

// Or create Customer Invoice for customer invoices
// bool customerResult = await service.CreateCustomerInvoice(document);

if (vendorResult)
{
Console.WriteLine($"Successfully processed document {document.Id}");
}
}
}
}
catch (Exception ex)
{
Console.WriteLine($"Error in main process: {ex.Message}");
}
finally
{
service.Dispose();
}
}
}
}

πŸ”§ Configuration Requirements​

NuGet Packages Required​

Add these NuGet packages to your project:

<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="System.Net.Http" Version="4.3.4" />

NetSuite Custom Fields Setup​

Create custom fields in NetSuite for tracking Docflo documents:

  1. Transaction Body Custom Fields:
    • Field ID: custbody_docflo_document_id

    • Type: Free-Form Text

    • Label: Docflo Document ID

    • Length: 50

    • Applies To: Vendor Bill, Invoice

    • Field ID: custbody_docflo_source_id

    • Type: Free-Form Text

    • Label: Docflo Source ID

    • Length: 50

    • Applies To: Vendor Bill, Invoice

NetSuite User Role Configuration​

Ensure your integration user role has the following permissions:

  1. Transactions:

    • Vendor Bill: Full
    • Invoice: Full
    • Purchase Order: View
    • Sales Order: View
  2. Lists:

    • Vendors: Full
    • Customers: Full
    • Items: View
    • Accounts: View
  3. Setup:

    • REST Web Services: Full
    • SuiteScript: Full
    • Integration Management: Full

Application Configuration​

Create an app.config file with your settings:

<?xml version="1.0" encoding="utf-8"?>
<configuration>
<appSettings>
<add key="DocfloApiUrl" value="https://api.docflo.ai" />
<add key="TenantId" value="your-tenant-id" />
<add key="ApiKey" value="your-api-key" />
<add key="NetSuiteAccountId" value="your-netsuite-account-id" />
<add key="NetSuiteConsumerKey" value="your-consumer-key" />
<add key="NetSuiteConsumerSecret" value="your-consumer-secret" />
<add key="NetSuiteTokenId" value="your-token-id" />
<add key="NetSuiteTokenSecret" value="your-token-secret" />
</appSettings>
</configuration>

πŸš€ Deployment Options​

Option 1: Windows Service​

Deploy as a Windows Service for continuous processing:

  1. Install the service using sc create or InstallUtil
  2. Configure service account with appropriate permissions
  3. Set up logging for monitoring and troubleshooting
  4. Schedule regular document polling

Option 2: Azure Function​

Deploy as an Azure Function for cloud-based processing:

  1. Create Azure Function App
  2. Configure timer trigger for scheduled execution
  3. Set application settings for configuration values
  4. Monitor execution through Azure portal

Option 3: Scheduled Task​

Use Windows Task Scheduler for periodic execution:

  1. Create a scheduled task to run the application
  2. Set appropriate triggers (hourly, daily, etc.)
  3. Configure security context for NetSuite access
  4. Monitor execution logs

πŸ“Š Monitoring and Logging​

Implement comprehensive logging using NLog or Serilog:

// Add to your service class
private static readonly NLog.Logger logger = NLog.LogManager.GetCurrentClassLogger();

public void LogIntegrationActivity(string message, Exception ex = null)
{
if (ex == null)
{
logger.Info(message);
}
else
{
logger.Error(ex, message);
}
}

πŸ” NetSuite Specific Considerations​

OAuth 1.0 Authentication​

NetSuite uses OAuth 1.0 for REST API authentication:

  • Consumer Key/Secret: From Integration Record
  • Token ID/Secret: From Access Token
  • Signature Method: HMAC-SHA256
  • Account ID: Your NetSuite account identifier

SuiteQL for Searching​

NetSuite provides SuiteQL for advanced searching:

  • Base URL: https://account-id.suitetalk.api.netsuite.com/rest/platform/v1/query/v1/suiteql
  • SQL-like syntax for querying records
  • Supports complex WHERE clauses and JOINs

Record Types​

NetSuite uses specific record types:

  • vendorbill - Vendor Bills (A/P)
  • invoice - Customer Invoices (A/R)
  • vendor - Vendor master records
  • customer - Customer master records
  • serviceitem - Service items
  • inventoryitem - Inventory items

Internal IDs​

NetSuite uses internal IDs for record references:

  • All record references use { internalId: "value" } format
  • Currency, accounts, subsidiaries all have internal IDs
  • Custom fields use field IDs like custbody_field_name

πŸŽ‰ Integration Complete! Your Oracle NetSuite system is now connected to Docflo.ai and can automatically process invoice documents, creating both vendor bills and customer invoices based on extracted document data.

πŸ” Troubleshooting​

Common Issues​

  1. OAuth Authentication Errors:

    • Verify Consumer Key and Consumer Secret from Integration Record
    • Check Token ID and Token Secret from Access Token
    • Ensure proper OAuth signature generation
    • Verify account ID in base URL
  2. SSL Certificate Issues:

    • Verify certificate installation in Windows Certificate Store
    • Check certificate chain completeness
    • Ensure proper certificate binding
  3. API Rate Limiting:

    • NetSuite has governance limits for API calls
    • Implement retry logic with exponential backoff
    • Monitor usage through NetSuite's governance dashboard
  4. Data Mapping Issues:

    • Validate extracted data from Docflo documents
    • Check required fields for NetSuite record creation
    • Ensure proper data type conversions
    • Verify internal ID mappings for currencies, accounts, etc.
  5. SuiteQL Query Issues:

    • Ensure proper SQL syntax in SuiteQL queries
    • Check field names and table names
    • Handle special characters in search values
    • Verify user permissions for queried records
  6. Custom Field Issues:

    • Verify custom fields exist in NetSuite
    • Check field IDs match exactly (case-sensitive)
    • Ensure fields are enabled for the record types
    • Verify user has access to custom fields

πŸ“ž Support​

For technical assistance with the Oracle NetSuite integration:

  • Contact Docflo.ai support team
  • Consult NetSuite SuiteCloud documentation
  • Review NetSuite REST API guides
  • Contact your NetSuite administrator or implementation partner
  • Visit NetSuite Help Center for SuiteScript and REST API resources